<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta content="IE=edge" http-equiv="X-UA-Compatible">
    <meta content="width=device-width, initial-scale=1.0" name="viewport">
    <title>A2A：让AI协作标准化</title>
    <link href="https://hoppinzq.com/static/images/favicon.ico" rel="shortcut icon" type="image/x-icon">
    <link href="https://hoppinzq.com/spider/css/bootstrap.min.css" rel="stylesheet">
    <link href="https://hoppinzq.com/static/css/plugins/lightbox.css" rel="stylesheet">
    <link href="https://hoppinzq.com/spider/css/LineIcons.3.0.css" rel="stylesheet">
    <link href="https://hoppinzq.com/spider/css/simple-bar.css" rel="stylesheet">
    <link href="https://hoppinzq.com/spider/css/footer.css" rel="stylesheet">
    <link href="https://hoppinzq.com/spider/css/main.css" rel="stylesheet">
    <link href="https://hoppinzq.com/20241/音乐节宣传HTML5模板/static/css/plyr.css" rel="stylesheet">
    <link href="https://hoppinzq.com/user/zhangqi3qw/zTree/zTreeStyle/animate.css" rel="stylesheet" type="text/css">

</head>
<style>
    div:focus, div:active, button:focus, button:active, a:focus, a:active {
        outline: none;
    }

    body, div {
        -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
    }

    section, .doc-main-wrapper {
        outline: 1px solid transparent;
    }

    .footer-menu-items {
        margin: 2px 0px;
    }

    .ztree ul li:before {
        left: -173px;
    }

    .ztree > li > p ~ ul > li > p ~ ul > li > p ~ ul > li > p ~ ul > li > p {
        background: #378639;
    }

    .explanations .single-explanation h4 {
        word-break: break-all;
    }

    .type-btn {
        color: #fff;
        position: absolute;
        top: 22px;
        right: 12px;
        border: 1px solid #eee;
        background: #61b864;
        border-radius: 5px;
        font-size: 12px;
        padding: 7px 16px;
        -webkit-transition: all 0.4s ease;
        transition: all 0.4s ease;
        font-family: 'Inter', sans-serif;
    }

    .zwagger {
        padding: 2em 0;
        background: #fff;
    }

    .zwagger-service .zwagger-method {
        border: none;
        box-shadow: none;
        border-radius: 0;
        margin: 0 0 15px 10px;
    }

    .zwagger-service .zwagger-method-header {
        padding: 0;
        border-radius: 30px;
    }

    .zwagger-service .panel-title a {
        display: block;
        padding: 12px 20px 12px 50px;
        background: #4fab45;
        font-size: 14px;
        font-weight: 600;
        color: #fff;
        border: 1px solid transparent;
        border-radius: 30px;
        position: relative;
        transition: all 0.3s ease 0s;
    }

    .zwagger-service .panel-title a.collapsed {
        background: #fff;
        color: #0d345d;
        border: 1px solid #ddd;
    }

    .zwagger-service .panel-title a:after,
    .zwagger-service .panel-title a.collapsed:after {
        content: "\f107";
        font-family: fontawesome;
        width: 55px;
        height: 55px;
        line-height: 55px;
        border-radius: 50%;
        background: #4cc251;
        font-size: 25px;
        color: #fff;
        text-align: center;
        border: 1px solid transparent;
        box-shadow: 0 3px 10px rgba(0, 0, 0, 0.58);
        position: absolute;
        top: -5px;
        left: -20px;
        transition: all 0.3s ease 0s;
    }

    .zwagger-service .panel-title a.collapsed:after {
        content: "\f105";
        background: #fff;
        color: #0d345d;
        border: 1px solid #ddd;
        box-shadow: none;
    }

    .zwagger-service .panel-body {
        padding: 20px 25px 10px 9px;
        background: transparent;
        font-size: 14px;
        color: #8c8c8c;
        line-height: 25px;
        border-top: none;
        position: relative;
    }

    .zwagger-service .panel-body p {
        padding-left: 25px;
        border-left: 1px dashed #8c8c8c;
    }

    .zwagger-service .panel-title .method-title {
        float: right;
        margin-right: 5%;
    }

    .zwagger-service-hide {
        float: right;
        margin-right: 5%;
        cursor: pointer;
    }

    .zwagger-param-hide {
        float: right;
        margin-right: 5%;
        cursor: pointer;
    }

    .zwagger-extra-hide {
        float: right;
        margin-right: 5%;
        cursor: pointer;
    }

    .zwagger-service .zwagger-form {
        margin-top: 10px;
    }

    .row {
        margin-left: 0 !important;
        margin-right: 0 !important;
    }

    input[type=checkbox] {
        width: 1rem;
        height: 1rem;
    }

    .table-params {
        border-bottom: 1px solid #000;
        margin-bottom: 5px;
    }

    .zwagger-api {
        margin-left: 5%;
    }

    .explanations .single-explanation.POST {
        border: 1px #49cc90 solid;
        background: rgba(73, 204, 144, .1);
    }

    .explanations .single-explanation.GET {
        border: 1px #61affe solid;
        background: rgba(97, 175, 254, .1);
    }

    .explanations .single-explanation.ALL {
        border-style: solid;
        border-width: 1px;
        border-image: linear-gradient(to bottom, #10d631, #61affe) 0.8;
        border-image-slice: 1;
        background: linear-gradient(90deg, #49CC9019, #61AFFE19);
    }

    .explanations .single-explanation.POST h4 {
        background: linear-gradient(90deg, #61b864, #10d631);
    }

    .explanations .single-explanation.GET h4 {
        background: linear-gradient(90deg, #619ab8, #61affe);
    }
</style>
<style>
    .explanations .single-explanation {
        border: 1px #49cc90 solid;
        background: rgba(73, 204, 144, .1);
        padding: 15px 0 0 15px !important;
    }

    .page-header {
        margin: 10px 0 20px;
        padding-bottom: 9px;
        border-bottom: 1px solid #e6e6e6;
    }

    .invoice-details {
        background-color: #f3f6f9;
        margin-bottom: 15px;
        border: 1px solid #ccc;
    }

    .invoice-details {
        padding-top: 15px;
        padding-bottom: 15px;
    }

    .no-margin {
        margin: 0 !important;
    }

    .table-bordered > tbody > tr > td, .table-bordered > tbody > tr > th {
        border: 1px solid #d1d3e0;
    }

    .table > tbody > tr > th, .table > tbody > tr td {
        border-bottom: 0 solid #f3f6f9;
    }

    .table > tbody > tr > td, .table > tbody > tr > th {
        border-top: 1px solid #f3f6f9;
        padding: 1rem;
        vertical-align: middle;
    }

    .total-payment h3 {
        padding-top: 15px;
        padding-bottom: 15px;
        border-top: 1px solid #ccc;
        display: inline-block;
        margin-top: 0;
    }

    .no-print {
        margin: 15px 10px;
    }

    .text-right {
        text-align: right !important;
    }

    .tips {
        border-radius: 4px;
        margin: .5rem 0;
        border-left: 4px solid #42b983;
        padding: 10px 15px;
        color: #8d8c8c;
        letter-spacing: 1px;
        background-color: rgba(66, 185, 131, .1);
    }
</style>
<style>
    :root {
        --plyr-color-main: #2f9064;
    }

    .plyr--full-ui input[type=range] {
        color: transparent;
        background: #fff;
        height: 3px;
    }

    .plyr--full-ui input[type=range]::before {
        content: "";
        position: absolute;
        top: 50%;
        transform: translateY(-50%);
        -webkit-appearance: none; /* Remove default appearance */
        width: var(--value); /* Full-width */
        height: 4px; /* Adjust the height */
        -webkit-border-radius: 100px;
        -moz-border-radius: 100px;
        border-radius: 100px;
        background: linear-gradient(90deg, #2f9064 0%, #2480a5 100%); /* Gradient color */
    }

    .plyr__progress__buffer {
        height: 3px;
    }

    .plyr--full-ui input[type=range]::-webkit-slider-thumb {
        -webkit-appearance: none; /* Remove default appearance */
        appearance: none;
        width: 15px; /* Thumb width */
        height: 15px; /* Thumb height */
        background: linear-gradient(90deg, #2f9064 0%, #2480a5 100%);
        border-radius: 50%; /* Round thumb */
        cursor: pointer; /* Pointer cursor on hover */
    }

    .plyr--full-ui input[type=range]::-moz-range-thumb {
        width: 15px; /* Thumb width */
        height: 15px; /* Thumb height */
        background: linear-gradient(90deg, #2f9064 0%, #2480a5 100%);
        border-radius: 50%; /* Round thumb */
        cursor: pointer; /* Pointer cursor on hover */
    }

    .ic-video-list {
        display: -webkit-box;
        display: -moz-box;
        display: -ms-flexbox;
        display: -webkit-flex;
        display: flex;
        flex-wrap: wrap;
        flex-direction: column;
        gap: 20px;
    }

    .ic-video-list .ic-video-list-item {
        display: -webkit-box;
        display: -moz-box;
        display: -ms-flexbox;
        display: -webkit-flex;
        display: flex;
        flex-wrap: wrap;
        gap: 30px;
    }

    .ic-video-list .ic-video-list-item .ic-video-song-info {
        flex: 1;
    }

    .ic-video-list .ic-video-list-item .ic-video-song-info .ic-video-song-name {
        display: -webkit-box;
        display: -moz-box;
        display: -ms-flexbox;
        display: -webkit-flex;
        display: flex;
        flex-wrap: wrap;
        -webkit-box-align: center;
        -moz-box-align: center;
        -ms-flex-align: center;
        -webkit-align-items: center;
        align-items: center;
        -webkit-box-pack: justify;
        -moz-box-pack: justify;
        -ms-flex-pack: justify;
        -webkit-justify-content: space-between;
        justify-content: space-between;
    }

    .ic-video-list .ic-video-list-item .ic-video-song-info .ic-video-song-name p {
        -webkit-transition: all 0.3s ease;
        -moz-transition: all 0.3s ease;
        -o-transition: all 0.3s ease;
        transition: all 0.3s ease;
    }

    .ic-video-list .ic-video-list-item .ic-video-song-info .ic-video-artist-name {
        display: -webkit-box;
        display: -moz-box;
        display: -ms-flexbox;
        display: -webkit-flex;
        display: flex;
        flex-wrap: wrap;
        -webkit-box-align: center;
        -moz-box-align: center;
        -ms-flex-align: center;
        -webkit-align-items: center;
        align-items: center;
        gap: 10px;
        padding-bottom: 8px;
    }

    .ic-video-list .ic-video-list-item .ic-video-song-info .ic-video-artist-name p,
    .ic-video-list .ic-video-list-item .ic-video-song-info .ic-video-artist-name span {
        font-size: 12px;
        display: inline-block;
        line-height: 100%;
    }

    .ic-video-list .ic-video-list-item .ic-video-song-info .ic-video-artist-name span {
        color: #ff275e;
    }

    .ic-video-list .ic-video-list-item .ic-video-song-info .ic-video-artist-social {
        display: -webkit-box;
        display: -moz-box;
        display: -ms-flexbox;
        display: -webkit-flex;
        display: flex;
        flex-wrap: wrap;
        -webkit-box-align: center;
        -moz-box-align: center;
        -ms-flex-align: center;
        -webkit-align-items: center;
        align-items: center;
        gap: 30px;
    }

    .ic-video-list .ic-video-list-item .ic-video-song-info .ic-video-artist-social a i {
        font-size: 14px;
        -webkit-transition: all 0.3s ease;
        -moz-transition: all 0.3s ease;
        -o-transition: all 0.3s ease;
        transition: all 0.3s ease;
    }

    .ic-video-list .ic-video-list-item .ic-video-song-info .ic-video-artist-social a:hover i {
        color: #ff275e;
    }

    .ic-video-list .ic-video-list-item:hover .ic-video-song-name p,
    .ic-video-list .ic-video-list-item:hover .ic-video-song-name span {
        color: #ff275e;
    }

    .ic-video-description {
        padding-top: 70px;
    }

    .ic-video-description h5 {
        padding-bottom: 30px;
        text-transform: uppercase;
        color: #ff275e;
    }

    .ic-video-description a {
        display: inline-block;
        padding-top: 30px;
    }

    .ic-view-more-videos {
        color: #fff;
        display: inline-block;
        padding-top: 10px;
    }

    .plyr--video .plyr__controls {
        background: rgba(0, 13, 34, 0.5);
        padding: 20px 6px;
    }
</style>
<body class="position-relative" data-bs-spy="scroll" data-bs-target="#navbar-example2" tabindex="0">

<div class="preloader">
    <div class="preloader-inner">
        <div class="preloader-icon">
            <span></span>
            <span></span>
        </div>
    </div>
</div>
<div aria-hidden="true" class="modal fade" id="testModal" style="display: none;">
    <div class="modal-dialog modal-lg modal_position">
        <div class="modal-content">
            <div class="modal-header">
                <h4 class="modal-title">敬请期待</h4>
            </div>
            <div class="modal-body">
                <div class="row">
                    <h6 style="margin: 5px 0;">何为HOPPIN的WEB托管网站？他能做什么？</h6>
                    <p>借助于 http://hoppin.cn 和 https://hoppinzq.com 两个域名,你可以将自己的网页直接托管到我的这两个域名下；
                        无须购买服务器、购买DNS解析和申请SSL证书；
                        可以使用hoppinzq.com自带的CDN提高你的网页加载速度；
                        可以使用我的网站的seo优化，将你的网页快速的被百度等搜索引擎收录；
                    </p>
                    <p>
                        要做到上面几点，你需要将你的网页通过接口上传到我的服务器上，然后选择java或者node作为后台（关于为什么没有Nginx，我想有必要说明:因为我需要对你的网页进行有效的管控和统计，并收集必要的信息）。
                        其他资源文件上传到我的cdn并通过接口更新目录和刷新内容。然后就可以通过
                        接口返回的地址访问你的页面了。</p>
                    <h6 style="margin: 5px 0;">现已支持unity的WebGL和手游托管！！！</h6>
                    <p>
                        在线尝试我做的unity游戏demo，带宽有限，因此加载速度缓慢。但是缓存做的好，一次加载，终身受益！微信移动端就不要点了，因为unity的webGL会造成微信的崩溃，但这并不是我的错。</p>
                    <p><a class="ticket" href="http://hoppin.cn/web/unity/yqcr/gameIndex.html" target="_blank">元气超人（基于Cocos）</a>
                    </p>
                    <p><a class="ticket" href="http://hoppin.cn/web/unity/2dxiaohuli/index.html" target="_blank">2D小狐狸Demo</a>
                    </p>
                    <p><a class="ticket" href="http://hoppin.cn/web/unity/mimixingdong/index.html" target="_blank">秘密行动Demo</a>
                    </p>
                    <p><a class="ticket" href="http://hoppin.cn/web/unity/xibudemo/index.html"
                          target="_blank">西部Demo</a></p>
                </div>
            </div>
            <div class="modal-footer">
                <button class="btn btn-secondary" data-dismiss="modal" onclick="$('#testModal').modal('hide');"
                        type="button">太棒了！我很期待！
                </button>
            </div>
        </div>
    </div>
</div>
<header class="header navbar-area others-pages">
    <div class="container">
        <div class="row align-items-center">
            <div class="col-lg-12">
                <div class="nav-inner">
                    <nav class="navbar navbar-expand-lg">
                        <a class="navbar-brand" href="">
                            <img alt="Logo" src="https://hoppinzq.com/static/images/logo/1640338711_113639.png">
                        </a>
                        <button aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"
                                class="navbar-toggler mobile-menu-btn" data-bs-target="#navbarSupportedContent"
                                data-bs-toggle="collapse" type="button">
                            <span class="toggler-icon"></span>
                            <span class="toggler-icon"></span>
                            <span class="toggler-icon"></span>
                        </button>
                        <div class="collapse navbar-collapse sub-menu-bar" id="navbarSupportedContent">
                            <ul class="navbar-nav ms-auto" id="nav">
                                <li class="nav-item">
                                    <a aria-label="Toggle navigation" class="active" href="a2a.html">文档</a>
                                </li>
                                <li class="nav-item">
                                    <a aria-label="Toggle navigation"
                                       href="https://gitee.com/hoppin/hoppinzq-gateway">开源地址</a>
                                </li>
                                <li class="nav-item">
                                    <a aria-label="Toggle navigation"
                                       onclick="$('#testModal').modal('show');" style="cursor: pointer">HOPPIN的WEB托管</a>
                                </li>
                            </ul>
                        </div>
                        <div class="button">
                            <a class="btn" data-target="#testModal" data-toggle="modal"
                               onclick="$('#testModal').modal('show');">加入HOPPIN的WEB托管 <i class="lni lni-circle-plus"></i></a>
                        </div>
                    </nav>

                </div>
            </div>
        </div>
    </div>
</header>

<main>
    <button class="menu-button menu-open">
        <span></span>
        <span></span>
        <span></span>
    </button>
    <div class="doc_overlay"></div>
    <div class="container doc_container">
        <div class="row">
            <div class="col-xl-3 col-lg-3 col-md-4 col-12">
                <aside class="doc-sidebar" data-simplebar="">
                    <button class="menu-button menu-close">
                        <span></span>
                        <span></span>
                    </button>
                    <nav class="sidenav" id="navbar-example2">
                        <ul class="nav flex-column">
                            <li>
                                <h6><i class="lni lni-list"></i> 写在前面 </h6>
                            </li>
                            <li class="nav-item">
                                <a class="nav-link active" href="#ys">演示</a>
                            </li>
                            <li class="nav-item">
                                <a class="nav-link" href="#quick-start">简介</a>
                            </li>
                            <li>
                                <h6><i class="lni lni-grid-alt"></i> 开始使用</h6>
                            </li>
                            <li class="nav-item">
                                <a class="nav-link" href="#Embedding">Embedding</a>
                            </li>
                            <li class="nav-item">
                                <a class="nav-link" href="#Embedding">Embedding封装</a>
                            </li>
                            <li class="nav-item">
                                <a class="nav-link" href="#rag">RAG</a>
                            </li>
                            <li class="nav-item">
                                <a class="nav-link" href="#Embedding">向量数据库</a>
                            </li>
                            <li class="nav-item">
                                <a class="nav-link" href="#rerank">重排序（Rerank）</a>
                            </li>
                            <li class="nav-item">
                                <a class="nav-link" href="#rewrite">多轮改写（Rewrite）</a>
                            </li>
                        </ul>
                    </nav>
                </aside>
            </div>
            <div class="col-xl-9 col-lg-9 col-12">
                <section class="main-section" tabindex="0">
                    <div class="doc-main-wrapper">
                        <div class="welcome mb-10">
                            <h1 class="mb-10">
                                A2A：<span class="template-name">让AI协作标准化 </span>
                            </h1>
                            <div class="author-info">
                                <img alt="#" class="img" src="https://hoppinzq.com/zui/static/picture/0.jpg">
                                <div>
                                    <p class="name">zhangqi</p>
                                    <span>2025/07/02</span>
                                </div>
                            </div>
                            <div class="mb-10 tips">
                                <p class="my-1">
                                    前言：如果一个人懂很多国家的语言，当你让他去把一篇文章翻译成阿拉伯语的时候，他就会出很多错，而且远远不如只精通阿拉伯语的人翻译的好，这就是术业有专攻。
                                    同样的，对于一个AI Agent，你如果赋予了它很多MCP工具，那么它出错的概率就会很高。<br>
                                    但是我们想，如果能把这些MCP工具分给多个AI Agent来使用，是不是可以避免上面的情况?不，可能会更糟，举个现实的例子：<br>
                                    一个软件开发项目组，通常会包括产品经理、架构师、UI设计师、前端开发、后端开发、测试和运维人员。每个角色都有其专业领域和职责，缺一不可。而且每次开发时都需要按照开发流程来进行，
                                    才能保证项目的质量和进度。一般的软件开发流程是这样的：需求分析、设计、编码、测试、部署、维护。如果项目的产品经理不能很好地调度团队成员，团队成员之间缺乏协作和沟通，团队成员不按照开发流程开发，与客户的需求对接不顺畅，
                                    那么项目就会出现问题，就会影响到项目的进度和质量，甚至导致项目失败。但是好在人们会在失败中总结经验，完善流程，提升团队的协作能力，从而在下一次项目中做得更好。<br>
                                </p>
                                <p class="my-1">
                                    我们说MCP可以让AI干活了，我们现在把软件开发的任务全部交给AI来干，会发生什么，我是可以预见的：
                                    AI之间的协作异常混乱，前后端同时写代码，涉及交互的接口乱调瞎写；
                                    在开发过程中缺乏跟用户沟通，随着对话增多，AI甚至开始自由发挥了，根本不按用户需求开发；
                                    每个AI都在自作主张，测试甚至不等部署就开展测试了，甚至有些AI开始互相攻击和扯皮了；<br>
                                    针对上面的乱象，我们对此无能为力，因为我们无法干预LLM执行MCP的顺序和对工具、提示词、上下文的理解能力。
                                    我们知道，人类的协作是有可以有章法，软件开发可以有流程，因此做的会越来越好，越来越规范。
                                    但是显然对AI来说并没有什么用，你不能指望AI在上下文里添加《人月神话》，就能在下次开发过程中干的更出色。
                                </p>
                            </div>
                            <hr>
                            <div class="doc-title mt-30" id="quick-start">
                                <h2>开始</h2>
                            </div>
                            <p class="my-1">
                                随着AI Agent的兴起，AI Agent已经完全可以介入人与任何应用交互的场景中，人只需要说“帮我订一张从xxx到xxx的火车票”，AI
                                Agent就会很智能地帮你查询余票并且向你确认，确认完成后就会自动生成订单。
                                所以我们说，几乎所有应用乃至操作系统，正迎来最大的一次范式转变——从人操作到人+AI
                                Agent协作。这个过程只需要：12306MCP（已实现），浏览器MCP（已实现），支付MCP（已实现），，，
                                甚至火车驾驶都可以用AI Agent来完成。
                            </p>
                            <p class="my-1">
                                但是，我们也发现，我只是简单地订一张火车票，就需要至少三个AI Agent来协作，而且需要严格按照顺序来执行，人对AI Agent的执行是完全被动的。
                            </p>
                            <p class="my-1">
                                低代码开发一直都面临一个尴尬的使用场景——程序员不想用，产品经理不会用，而且几乎不能满足一些复杂的需求
                                而随着MCP的兴起，AI Agent已经完全可以介入人与任何应用交互的场景中，这使得低代码开发迎来最大的一次范式转变——从人操作到人+AI Agent协作。
                                在需求分析、设计、编码、测试、部署、维护的整个软件开发流程中，都可以用AI Agent来协作完成。
                                AI，正让所有人，都可以构建自己的应用。
                            </p>
                            <div class="doc-title mt-30" id="wyyyy">
                                <h2>第一个钓鱼网站——网易云音乐</h2>
                            </div>
                            <p class="my-1">
                                原理大家都搞懂了，那么编写一个钓鱼网站来实现盗号的目的就非常简单了。我们以网易云音乐为例，我们的需求是：
                            <ol class="my-1" style="list-style: auto; padding-left: 20px;">
                                <li class="my-1">1、用户在我们的钓鱼网站扫描二维码。</li>
                                <li class="my-1">2、在扫描二维码的app端伪造网易云登录授权请求（若用户使用网易云音乐的扫码，则忽略此步）。</li>
                                <li class="my-1">3、在用户毫不知情的情况下：①偷着让用户关注我（若用户已关注，则忽略）。
                                    ②、窃取用户音乐标签。③、获取用户的关注列表。④、给每个关注的用户发送一条信息，内容是“我是大傻逼哈哈哈，请点击xxx链接”。（太损，只让用户给我发就行了）
                                </li>
                            </ol>
                            实际上，3实现起来非常简单，在网易云音乐官网上通过F12开发者工具就可以看到。但是调用这些接口是需要用户凭证的，所以我们的终极目标是：
                            ①、用户凭证是如何获取的。
                            ②、用户凭证是如何传递的。
                            </p>
                            <p class="my-1">
                                显然前后端开发人员都准备跃跃欲试了，但是，非专业人员千万不要着急参战，因为网易云是有风控的！！！所以一定要用小号和代理IP来测试。这里不带大家展开了，感兴趣可以联系我。
                            </p>
                            <a class="img-light"
                               href="https://hoppinzq.com/embedding/image/img.png"
                               rel="lightbox"><img alt=""
                                                   class="img" src="https://hoppinzq.com/embedding/image/img.png"/></a>
                            <p class="my-1" id="Embedding">可以看到，LLM的回答基本符合我们预期，而且我们使用System
                                Prompt可以让LLM关注更多标签和细节，就非常灵活（比如我们让LLM在计算相似度时，更侧重于作品的国家，那么火影忍者的相似度就会更高，从而排在前面）。
                                但是我们不应该指望用提示词来做，因为有上下文的限制，而且LLM不总是返回期望的JSON格式。我就不卖关子了，如果你阅读过chatGPT的文档，
                                你会发现一个叫Embeddings的东西。学过人工智能或者自然语言处理的同学的应该知道这个Embedding这个东西。</p>
                            <p>OpenAI说 Embedding 通常用于以下场景：</p>
                            <ol class="my-1" style="list-style: auto; padding-left: 20px;">
                                <li class="my-1"><b>搜索</b>（结果按查询字符串的相关性进行排序）</li>
                                <li class="my-1"><b>聚类</b>（将文本字符串按相似性分组）</li>
                                <li class="my-1"><b>推荐</b>（推荐具有相关文本字符串的项目）</li>
                                <li class="my-1"><b>异常检测</b>（识别相关性较小的异常值）</li>
                                <li class="my-1"><b>多样性测量</b>（分析相似度分布）</li>
                                <li class="my-1"><b>分类</b>（文本字符串按其最相似的标签进行分类）</li>
                            </ol>
                            <p class="my-1 tips">
                                Embedding到底是什么呢？它的翻译是"嵌入"，通常用在自然语言处理和机器学习领域。我们处理字、词、短语、句子的时候，
                                通常不是直接操作这些字符，而是把它们映射成数字或者二进制（如，把'我'记为12，或者记为00010010），然后我们就可以通过算法处理关系了。
                                embedding则是把这些数据统一处理成向量，在数学上进行比较和计算，从而对文本建立关联关系。</p>
                            <p>
                                总之Embedding本质就是一组向量，是LLM用来表示一段文本的映射。你可能有一点思路了，两个向量之间是有数学意义的，通过比较两个文本的向量，就可以知道它们的相似度。没错，就是这样！<br/>
                            </p>
                            <div class="my-1 tips">
                                <p>相似度，准确来说是语义相似度。在自然语言处理领域，我们一般使用cosine相似度作为语义相似度的度量，评估两个向量在语义空间上的分布情况。它的表达式是这样的：</p>
                                <a class="img-light"
                                   href="https://hoppinzq.com/embedding/image/img_1.png"
                                   rel="lightbox"><img alt=""
                                                       class="img" src="https://hoppinzq.com/embedding/image/img_1.png"/></a>
                                <p class="my-1">根据表达式。就可以写出代码：</p>
                                <pre class="code-box" data-simplebar=""><button class="copy-btn">复制</button>
<code>/**
 * 余弦相似度算法（伪代码）
 * @param vector1 向量1
 * @param vector2 向量2
 * @return 相似度值，范围在[-1,1]之间，值越大表示越相似
 */
public static double cosineSimilarity(double[] vector1, double[] vector2) {
    RealVector realVector1 = new ArrayRealVector(vector1);
    RealVector realVector2 = new ArrayRealVector(vector2);
    double dotProduct = realVector1.dotProduct(realVector2);
    double norm = realVector1.getNorm() * realVector2.getNorm();
    double similarity = dotProduct / norm;
    return similarity;
}</code>
</pre>
                                <p>cosine相似度计算简便，数学意义便于理解，适用于高维数据，因此在文本分析场景表现良好。
                                    但是它暴力计算很占资源，对特征相关性和权重不敏感，没法根据相关度排序（比如我搜索张祺：我期望让作者为张祺的排在中间，内容包含张祺的排在后面，给广告费的排在最前面）。</p>
                            </div>
                            <p class="my-1">
                                那么现在问题来了，如何求给定文本的向量？也就是embedding？没错，这就是LLM要帮我们做的事情。我们现在只需要让LLM告诉我们，输入文本的向量就可以了。</p>
                            <p class="my-1">显然，openai提供了查询输入文本向量的查询接口：</p>
                            <a class="img-light"
                               href="https://hoppinzq.com/embedding/image/img_2.png"
                               rel="lightbox"><img alt=""
                                                   class="img" src="https://hoppinzq.com/embedding/image/img_2.png"/></a>
                            <p><b>参数说明：</b></p>
                            <ol class="my-1" style="list-style: auto; padding-left: 20px;">
                                <li class="my-1">
                                    <p class="my-1">①、model是必传的，也就是模型名称，（OpenAI）有下面4个模型可以用：</p>
                                    <div class="table-wrapper table-responsive">
                                        <table class="table">
                                            <thead>
                                            <tr>
                                                <th>模型</th>
                                                <th>输出维度</th>
                                            </tr>
                                            </thead>
                                            <tbody>
                                            <tr>
                                                <td>Ada</td>
                                                <td>1024</td>
                                            </tr>
                                            <tr>
                                                <td>Babbage</td>
                                                <td>2048</td>
                                            </tr>
                                            <tr>
                                                <td>Curie</td>
                                                <td>4096</td>
                                            </tr>
                                            <tr>
                                                <td>Davinci</td>
                                                <td>12288</td>
                                            </tr>
                                            </tbody>
                                        </table>
                                    </div>
                                    <p class="my-1">Davinci 是能力最强的，但比起其他模型来，更慢更昂贵。Ada 能力最弱，但明显更快更便宜。</p>
                                    <p class="my-1">在该案例中，我是使用的text-embedding-ada-002模型，002表示第二代模型，维度是1536。</p>
                                    <p class="my-1 tips">维度是指数组的长度。维度越高越准确，但是计算量大。反之越小越不准确，但计算量小，接口响应速度更快。</p>
                                </li>
                                <li class="my-1">②、input是必传的，传一个数组，也就是支持传多个文本，会返回所有文本对应的embedding。</li>
                                <li class="my-1">③、user不是必传的，用于身份验证，主要是为了防止滥用。</li>
                            </ol>
                            <p class="my-1">调用一下接口，响应如下：</p>
                            <a class="img-light"
                               href="https://hoppinzq.com/embedding/image/img_3.png"
                               rel="lightbox"><img alt=""
                                                   class="img" src="https://hoppinzq.com/embedding/image/img_3.png"/></a>
                            <p class="my-1">拿到5个向量，分别计算一下相似度（越接近1越相似）：</p>
                            <a class="img-light"
                               href="https://hoppinzq.com/embedding/image/img_4.png"
                               rel="lightbox"><img alt=""
                                                   class="img" src="https://hoppinzq.com/embedding/image/img_4.png"/></a>
                            <p class="my-1">可以看到，诸葛亮和三国演义的相似度是最大的，基本符合我们认知。</p>
                            <p class="my-1">对于开发人员，下面这个应该都懂。对于Java如何连接MySQL，JDBC应该是最相似、最优的答案。跟Navicat就没啥关系了。
                                显然也符合我们预期。</p>
                            <a class="img-light"
                               href="https://hoppinzq.com/embedding/image/img_5.png"
                               rel="lightbox"><img alt=""
                                                   class="img" src="https://hoppinzq.com/embedding/image/img_5.png"/></a>
                            <hr>
                            <div class="doc-title mt-30" id="code-embedding">
                                <h2>封装embedding</h2>
                            </div>
                            <p class="my-1">
                                通过上面的介绍，我们已经知道了embedding的能力，接下来就是封装一下，方便我们在代码里调用。embedding依赖于LLM，
                                因此你必须会OpenAI规范的API调用，会获取不同LLM提供的embedding，这里以OpenAI的API为例。其次你还需要会使用线程池，
                                来优化繁多的相似度计算。这可能是你接触的第一个“CPU密集型”（数据库是IO密集型）的操作了，你可以深刻感受到初始化线程池线程数量，导致的速度的差距，
                                我们使用最简单的并行计算来优化。
                            </p>
                            <hr>
                            <a class="img-light mb-10"
                               href="https://hoppinzq.com/embedding/image/img_6.png"
                               rel="lightbox"><img alt=""
                                                   class="img" src="https://hoppinzq.com/embedding/image/img_6.png"/></a>

                            <p class="my-1">1、我提炼了4个核心方法，需要大家实现，分别是：
                                1、获取输入文本的向量。2、获取两个向量的相似度。3、初始化向量。4、查询。
                            </p>
                            <p class="mb-10">
                                借助hoppinAI，我们很容易可以获取到多个文本的向量。借助AI，很容易写出一个可用的相似度计算的算法。
                            </p>
                            <p class="mb-10">
                            <pre class="code-box" data-simplebar=""><button class="copy-btn">复制</button>
<code>public interface MyEmbedding {

    /**
     * 获取多个文本的向量表示
     * @param inputs 文本内容列表
     * @return
     */
    default List&lt;List&lt;Double>> getEmbeddings(List&lt;String> inputs){
        if (CollUtil.isEmpty(inputs)) {
            return Collections.emptyList();
        }
        OpenAiService service = new OpenAiService(FunctionCallCommon.apiKey,
                Duration.ofSeconds(60),
                FunctionCallCommon.openaiProxy);
        EmbeddingResult embeddings = service.createEmbeddings(EmbeddingRequest.builder()
                .model(FunctionCallCommon.embedding_model)
                .input(inputs)
                .build());
        List&lt;Embedding> data = embeddings.getData();
        List&lt;List&lt;Double>> embeddingList = data.stream()
                .map(Embedding::getEmbedding)
                .collect(Collectors.toList());
        return embeddingList;
    }

    /**
     * 计算两个向量的余弦相似度
     * @param vector1
     * @param vector2
     * @return
     */
    default double getSimilarity(double[] vector1, double[] vector2) {
        if (CollUtil.isEmpty(Collections.singleton(vector1))
                || CollUtil.isEmpty(Collections.singleton(vector2))
                || vector1.length != vector2.length) {
            return 0.0;
        }
        double dotProduct = 0.0;
        double normA = 0.0;
        double normB = 0.0;
        for (int i = 0; i < vector1.length; i++) {
            dotProduct += vector1[i] * vector2[i];
            normA += Math.pow(vector1[i], 2);
            normB += Math.pow(vector2[i], 2);
        }
        return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
    }

    /**
     * 批量插入向量数据
     * @param inputs 文本内容列表
     */
    void insertBatch(List&lt;String> inputs);

    /**
     * 查询向量数据
     * @param input 文本内容
     * @param topK 返回结果数量
     * @param threshold 相似度阈值，低于此值的向量不会被返回
     * @return
     */
    List&lt;String> search(String input, int topK,float threshold);
}
</code></pre>
                            </p>
                            <p class="my-1">
                                对于第三步，关键是如何存储向量，大家放在数据库里就好，代码可以参考我的实现。关键是第四步查询，但是显然我也不想在这里写代码，
                                自己去看源代码吧（笑。我只说一下大体的思路：
                            <ol class="my-1" style="list-style: auto; padding-left: 20px;">
                                <li class="my-1"> 1、先从数据库里把所有向量Es查询出来。</li>
                                <li class="my-1"> 2、计算输出的向量E1</li>
                                <li class="my-1"> 3、将所有的向量Es按一定数量平分</li>
                                <li class="my-1"> 4、创建线程池，按CPU核心数分配核心线程数和最大线程数</li>
                                <li class="my-1"> 5、创建一个实现Callable接口内部类，用来计算相似度</li>
                                <li class="my-1"> 6、将所有的向量Es分配到线程池中，计算相似度并汇总</li>
                                <li class="my-1"> 7、将相似度按从大到小排序，筛出大于阈值的向量Ess</li>
                                <li class="my-1"> 8、返回向量Ess的原数据</li>
                            </ol>
                            </p>
                            <a class="img-light"
                               href="https://hoppinzq.com/embedding/image/img_8.png"
                               rel="lightbox"><img alt=""
                                                   class="img" src="https://hoppinzq.com/embedding/image/img_8.png"/></a>
                            <p class="my-1">
                                可以看到，使用Embedding实现一个小型的推荐系统的思路就是这样的。
                            </p>
                            <hr/>
                            <div class="doc-title pt-30" id="rag">
                                <h2>RAG</h2>
                            </div>
                            <p class="my-1">
                                我们知道，LLM的回答非常依赖于训练它的知识集。你去问它张祺是谁，大模型大概率会顾左右而言他，要么不认识，要么瞎BB。
                                不认识还好，我们可以通过微调（FineTuning）大模型来让它认识张祺。瞎BB就不太好了，很可能会误导用户，这就是我们常说的大模型的幻觉。
                                但是如果我们能让大模型在回答问题时，能够参考一些外部知识库，那就可以大大提升它的回答质量，这就是RAG（Retrieval Augmented
                                Generation），检索增强生成。
                                RAG具有较高的可解释性和定制能力，可大幅降低LLM的幻觉，适用于企业内部知识库、智能客户、代码助手等场景。
                            </p>
                            <a class="img-light"
                               href="https://hoppinzq.com/embedding/image/img_7.png"
                               rel="lightbox"><img alt=""
                                                   class="img" src="https://hoppinzq.com/embedding/image/img_7.png"/></a>
                            <p class="my-1">
                                RAG的思想是：在生成回答之前，先从知识库中检索相关信息，然后将这些信息作为上下文传递给大模型，让它在生成回答时参考这些信息。
                                这样一来，大模型就可以基于最新的知识库来回答问题，而不是仅仅依赖于它训练时的知识。
                            </p>
                            <p class="my-1">
                                这个操作看起来很简单，实际的技术点很多，当然你只需要会用就行了，但是如果你想自己实现RAG，就需要关注：
                            <ol class="my-1" style="list-style: auto; padding-left: 20px;">
                                <li class="my-1"> 1、文档是如何解析的</li>
                                <li class="my-1"> 2、内容是如何检索的</li>
                            </ol>
                            </p>
                            <p class="my-1">
                                文档是如何解析的？大家可能有个疑问，就是RAG跟文档有啥关系。我其实忘了说了，RAG的主要载体，就是各种文档。包括PPT、word、excel、pdf、md、text、图片等格式。
                                解析文档的目的，就是为了方便检索。
                                用OCR解析文件内容是一个思路，但是OCR会丢失结构、排版和富文本，我们不考虑。
                                我们知道文档可以被xml或者json所描述，因此如何将文档转为xml或者json是一个问题，你用poi或者别的类库很容易将文档转为xml。
                                但是xml和json比较繁琐，不利于拆分和生成向量，就很容易丢失文档的上下文。因此，需要将文档转为markdown。markdown在描述文档结构方面非常出色，
                                层次感很强，也支持表格、图片、图表、代码片段，而且很小，是最适合AI理解和使用的资源。
                                AI生成PPT的底层实现，就是AI通过提示词和知识库生成markdown（或json），然后markdown转PPT。
                            </p>
                            <p class="my-1">
                                有了markdown，我们很容易通过标题拆分文档，从而生成向量。但是有的时候，文档内容并不总是从上到下排，有可能一页里塞了两块内容，这时候你解析markdown可要小心，
                                你需要单独定制解析规则，尤其是一些标准的模版文件。有的时候，文档里都是图表、表格、图片，也是需要单独解析的。
                            </p>
                            <p class="my-1">
                                代码？没有！这里太小我放不下。你直接去调现成的吧。至于内容是如何检索的，你把上面向量那块学明白就行了。
                            </p>
                            <div class="doc-title pt-30" id="rerank">
                                <h2>重排序</h2>
                            </div>
                            <p class="my-1">
                                如果你已经实现了文档解析和检索，那么实际上你就已经具备了RAG的能力。重排序是检索和生成之间的操作，是对候选文档进行二次筛选和排序。
                                目的则是将最相关、最有用的内容排在前面，以供后面的LLM学习使用。我们在上面计算embedding的相似度的时候提了一嘴，就是我们使用的cosine相似度
                                没法对特征相关性和权重排序。举个例子：我搜索了“定积分”，通过检索就会把《初等数学》、《高等数学》、《线性代数》、
                                《概率论》等等相关章节检索出来。但是我是大学生，所以我们需要通过用户画像，微调检索的结果，这就是重排序的工作。
                                经过重排序后《高等数学》的“微积分章节”就会排在最前面。
                            </p>
                            <a class="img-light"
                               href="https://hoppinzq.com/embedding/image/img_9.png"
                               rel="lightbox"><img alt=""
                                                   class="img" src="https://hoppinzq.com/embedding/image/img_9.png"/></a>
                            <p class="my-1">
                                作为初学者，您无需关注重排序的实现细节。你只要知道原理，然后可以使用<span class="fw-bold">精调提示词</span>的大模型作为重排序大模型。
                            </p>
                            <div class="doc-title pt-30" id="rewrite">
                                <h2>多轮改写和指代消缺</h2>
                            </div>
                            <p class="my-1">
                                由于RAG在解析文档，拆分文档的时候，会丢失一些上下文信息，比如段落之间的逻辑关系、段落内的语义关系等。用户在询问的时候，就可能会出现用户的语义空间和文档的语义空间不统一的情况。
                                比如：用户询问“北京奥运会的举办年份”，大模型的答案是“2008年”。但是我再询问”当时的美国总统是谁，今年干了什么事？“，你很容易知道是”奥巴马“，干了啥事自己就去百度了。
                                但是大模型知道“当时”是指什么吗？借助上下文，它还真知道，大模型会改写为“2008年的美国总统是谁，今年干了什么事？”。把“当时”改写为“2008年”，就是指代。
                            </p>
                            <a class="img-light"
                               href="https://hoppinzq.com/embedding/image/img_10.png"
                               rel="lightbox"><img alt=""
                                                   class="img" src="https://hoppinzq.com/embedding/image/img_10.png"/></a>
                            <p class="my-1">
                                OK，看上图，这就是一个简单的对话，只不过assistant是被LLM增强的RAG的答案。有的同学可能知道LLM的上下文可以解决这个问题，但是你要想明白，LLM发挥的作用
                                是在RAG的增强环节，而不是在RAG的检索环节。我们要向量化的，是提问的"当时的这款游戏新出了什么英雄，今年又更新了啥"这一问题。因此，必须在生成向量前，对用户的提问进行指代消缺。
                            </p>
                            <p class="my-1">
                                作为初学者，您无需关注多轮改写的实现细节。你只要知道原理，然后可以使用<span class="fw-bold">精调提示词</span>的大模型作为多轮改写大模型。
                            </p>
                            <div class="doc-title pt-30" id="hoppin_knowledge">
                                <h2>HOPPIN的自建知识库</h2>
                            </div>
                            <p class="my-1">
                                （注意：通过HOPPINAI上传的知识库文档都会存到我的服务器上，自己玩玩就行了，不要上传涉密文件。因为我没有对知识库做标签，也就是说，你的文件内容是可能被其他人检索到的）</p>
                            <p class="my-1">
                                将我上面提供的原子接口组装起来，你就能实现一个RAG（见RAG的那个图）。具体答案就是把用户提问A和上下文B进行指代消缺得到C；再将C向量化得到D；D和多个文档分片进行相似度计算，筛选所有大于某阈值
                                的文档分片[E]；用D和[E]进行重排序得到[E`]；将系统提示词、用户提问A和[E`]作为LLM的上下文，让LLM响应即可。
                            </p>
                            <p class="my-1">
                                RAG辅助LLM还是比较有意思的，比如你想学一个很新的组件，文档你也不想看，网上的教程也很少。你就可以把网页上的文档内容转markdown格式丢给AI，让它去学着用这个组件（笑）。
                                此外RAG配合ToolCall或MCP效果拔群，你可以让AI很智能地选择不同的知识库来回答问题。比如我有一个MCP Server是查询张祺的知识库，那我可以声明这个MCP
                                Tools，让AI
                                在用户询问关于张祺的问题的时候，才去查询张祺的知识库。
                            </p>
                        </div>
                    </div>
                </section>
            </div>
        </div>
    </div>
</main>
<footer class="footer">
    <div class="container">
        <div class="row">
            <div class="col-lg-8 offset-lg-2 col-12">
                <div class="footer-content">
                    <div class="logo">
                        <a href="javascript:void(0)">
                            <img alt="logo" src="https://hoppinzq.com/static/images/logo/1640338711_113639.png">
                        </a>
                    </div>
                    <p>如果觉得该文档很有帮助，可通过以下方式联系我！Enjoy coding！</p>
                    <ul class="social">
                        <li><a href="https://gitee.com/hoppin"><i class="lni lni-github-original"></i></a></li>
                        <li><a href="javascript:void(0)" onclick="alert('微信号：zhangqiff19')" title="zhangqiff19"><i
                                class="lni lni-wechat"></i></a></li>
                        <li><a href="tel:15028582175"><i class="lni lni-phone"></i></a></li>
                        <li><a href="mailto:anmiezata@163.com"><i class="lni lni-envelope"></i></a></li>
                    </ul>
                </div>
            </div>
        </div>
    </div>
    <div class="copy-right bg-dark">
        <div class="container">
            <div class="row">
                <div class="col-12">
                    <p><a href="https://beian.miit.gov.cn/" target="_blank">Copyright © HOPPINZQ.
                        ICP主体备案号:鲁ICP备2021023310号</a></p>
                </div>
            </div>
        </div>
    </div>
</footer>
<a class="scroll-top" href="#">
    <i class="lni lni-chevron-up"></i>
</a>
<script src="https://hoppinzq.com/spider/js/jquery.min.js"></script>
<script src="https://hoppinzq.com/spider/js/popper.min.js"></script>
<script src="https://hoppinzq.com/spider/js/bootstrap.min.js"></script>
<script src="https://hoppinzq.com/spider/js/simple-bar.js"></script>
<script src="https://hoppinzq.com/static/js/plugins/lightbox-2.6.min.js"></script>
<script src="https://hoppinzq.com/static/js/vendor/bootstrap.min.js" type="text/javascript"></script>
<script src="https://hoppinzq.com/20241/音乐节宣传HTML5模板/static/js/plyr.min.js"></script>

<script>

    const menuButtonOpen = document.querySelector(".menu-open");
    const menuButtonClose = document.querySelector(".menu-close");
    const sidebar = document.querySelector(".doc-sidebar");
    const docOverlay = document.querySelector(".doc_overlay");

    menuButtonOpen.addEventListener("click", () => {
        sidebar.classList.add("show");
        docOverlay.classList.add('open');
    });
    menuButtonClose.addEventListener("click", () => {
        sidebar.classList.remove("show");
        docOverlay.classList.remove('open');
    });
    docOverlay.addEventListener("click", () => {
        sidebar.classList.remove("show");
        docOverlay.classList.remove('open');
    });

    const copyButton = document.querySelectorAll('.copy-btn');
    copyButton.forEach(element => {
        element.addEventListener('click', (e) => {
            $(copyButton).each(function (index, elem) {
                $(elem).text("复制");
            });
            const elem = e.target.parentElement.children[1].innerText;
            const el = document.createElement('textarea');
            el.value = elem;
            document.body.appendChild(el);
            el.select();
            document.execCommand("copy");
            $(element).text("复制成功！");

            document.body.removeChild(el)
        })
    });
    let tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
    let tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
        return new bootstrap.Tooltip(tooltipTriggerEl)
    })

    $(function () {
        $.fn.append2 = function (html, callback) {
            let originalHtmlLength = $("body").html().length;
            this.append(html);
            let nums = 1;
            let timer1 = setInterval(function () {
                nums++;
                let clearIntervalfun = function () {
                    clearInterval(timer1)
                    callback();
                }
                let flag = originalHtmlLength != $("body").html().length || nums > 1000;
                flag && clearIntervalfun()
            }, 1)
        };
        $.fn.prepend2 = function (html, callback) {
            let originalHtmlLength = $("body").html().length;
            this.prepend(html);
            let nums = 1;
            let timer1 = setInterval(function () {
                nums++;
                let clearIntervalfun = function () {
                    clearInterval(timer1)
                    callback();
                }
                let flag = originalHtmlLength != $("body").html().length || nums > 1000;
                flag && clearIntervalfun()
            }, 1)
        };
        window.setTimeout(fadeout, 500);
        $(".img-light").on("click", function () {
            let $img = $(this).find("img")[0];
            $(".lb-outerContainer").width($img.width * 2).height($img.height);
            $("#lightbox").css("position", "fixed");
            $("#lightbox").css("top", "400px !important");//先写死
        });
        window.onscroll = function () {
            let backToTo = document.querySelector(".scroll-top");
            if (document.body.scrollTop > 50 || document.documentElement.scrollTop > 50) {
                backToTo.style.display = "flex";
            } else {
                backToTo.style.display = "none";
            }
        };
        let navbarToggle = document.querySelector(".mobile-menu-btn");
        navbarToggle.addEventListener('click', function () {
            navbarToggle.classList.toggle("active");
        });
        initVideo();
    })

    function initVideo() {
        const player = new Plyr(".zq-ic-video-player");
    }

    function fadeout() {
        document.querySelector('.preloader').style.opacity = '0';
        document.querySelector('.preloader').style.display = 'none';
    }
</script>

</body>
</html>
